package com.me.web.server.netty.tcp;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;


/**
 * Netty TCP客户端
 *  创建一个Netty客户端类，NettyTcpClient
 * @author chenlei
 */
@Slf4j
@Component
public class NettyTcpClient implements CommandLineRunner {

    /**
     * 最大连接次数
     */
    private final int maxConnectTimes = 10;

    /**
     * 地址
     */
    private final String host = "192.168.2.154";

    /**
     * 端口
     */
    private final int port = 1250;


    @Override
    public void run(String... args) {
        // 创建一个处理 I/O 操作的 EventLoopGroup，用于处理客户端的 I/O 操作和事件
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            // 创建 Bootstrap 对象，用于配置和启动 Netty 客户端
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    // 使用 NioSocketChannel 作为通道类型，基于 NIO 的 Socket 通道
                    .channel(NioSocketChannel.class)
                    // 设置 TCP 选项，如 TCP_NODELAY 开启无延迟模式，提高性能
                    .option(ChannelOption.TCP_NODELAY, true)
                    // 为 ChannelPipeline 添加处理器，用于处理通道的数据
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            // 添加帧解码器，处理粘包和拆包问题，这里以 \n 作为分隔符
                            ch.pipeline().addLast(new DelimiterBasedFrameDecoder(8192, Unpooled.copiedBuffer("\n".getBytes())));
                            // 添加字符串解码器，将字节数据解码为字符串
                            ch.pipeline().addLast(new StringDecoder());
                            // 添加字符串编码器，将字符串编码为字节数据
                            ch.pipeline().addLast(new StringEncoder());
                            // 添加自定义的处理器，用于处理接收到的数据
                            ch.pipeline().addLast(new NettyTcpClientHandler(bootstrap, host, NettyTcpClient.this));
                        }
                    });

            // 连接到服务器的指定端口范围
            final AtomicInteger connectTimes = new AtomicInteger(0);
            // 尝试连接到服务器，并添加连接结果监听器
            bootstrap.connect(host, port).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    // 连接成功时打印日志
                    log.info("成功连接到端口: {}", port);
                } else {
                    // 连接失败时打印日志
                    log.error("连接到服务器时出错，端口: {}", port);
                    // 连接失败时，使用 eventLoop 安排重连任务，60 秒后重连
                    if (connectTimes.get() < maxConnectTimes) {
                        connectTimes.incrementAndGet();
                        future.channel().eventLoop().schedule(() -> {
                            // 重连逻辑，再次尝试连接
                            bootstrap.connect(host, port);
                        }, 60, TimeUnit.SECONDS);
                    } else {
                        log.error("已达到最大连接次数，停止重连，端口: {}", port);
                    }
                }
            });
        } catch (Exception e) {
            // 发生异常时打印错误日志
            log.error("Netty 客户端出错", e);
        }
    }


    /**
     * 定义重新连接方法
     *
     * @param bootstrap 用于配置和启动 Netty 客户端的 Bootstrap 对象
     * @param host      服务器的主机地址
     * @param port      要连接的服务器端口号
     * @param future    表示连接操作的 ChannelFuture 对象
     */
    void reconnect(Bootstrap bootstrap, String host, int port, ChannelFuture future) {
        final AtomicInteger connectTimes = new AtomicInteger(0);
        try {
            bootstrap.connect(host, port).addListener((ChannelFutureListener) f -> {
                if (f.isSuccess()) {
                    log.info("重连成功，端口: {}", port);
                    connectTimes.set(0);
                } else {
                    if (connectTimes.get() < maxConnectTimes) {
                        connectTimes.incrementAndGet();
                        future.channel().eventLoop().schedule(() -> reconnect(bootstrap, host, port, f), 60, TimeUnit.SECONDS);
                    } else {
                        log.error("已达到最大连接次数，停止重连，端口: {}", port);
                    }
                }
            });
        } catch (Exception e) {
            log.error("重连时发生异常，端口: {}", port);
            reconnect(bootstrap, host, port, future);
        }
    }
}

